package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.album.client.CategoryFeignClient;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.PinYinUtils;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.AlbumIndexRepository;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;

    @Autowired
    private UserInfoFeignClient userInfoFeignClient;

    @Autowired
    private CategoryFeignClient categoryFeignClient;

    @Autowired
    private AlbumIndexRepository albumIndexRepository;

    //  需要自定义线程池? 为什么要使用自定义线程池?
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Autowired
    private SuggestIndexRepository suggestIndexRepository;


    @Override
    public List<Map<String, Object>> channel(Long category1Id) {
        //  远程调用获取一级分类下对应的三级分类Id集合；
        Result<List<BaseCategory3>> result = categoryFeignClient.findTopBaseCategory3(category1Id);
        Assert.notNull(result, "远程调用失败");
        List<BaseCategory3> category3List = result.getData();
        Assert.notNull(category3List, "查询失败");
        //  将category3List这个集合变为Map 形式 key=category3Id value=category3;
        //        Map<Long, BaseCategory3> category3Map = category3List.stream().collect(Collectors.toMap(basecategory3 -> {
        //            return basecategory3.getId();
        //        }, basecategory3 -> {
        //            return basecategory3;
        //        }));
        Map<Long, BaseCategory3> category3Map = category3List.stream().collect(Collectors.toMap(basecategory3 -> basecategory3.getId(), basecategory3 -> basecategory3));
//        Map<Long, BaseCategory3> category3Map2 = category3List.stream().collect(Collectors.toMap(BaseCategory3::getId, basecategory3 -> basecategory3));


        //  List<Long> category3IdList = category3List.stream().map(BaseCategory3::getId).collect(Collectors.toList());
        //   List<Long> -->  List<FieldValue>
        //  List<FieldValue> fieldValueList = category3IdList.stream().map(category3Id -> FieldValue.of(category3Id)).collect(Collectors.toList());
        List<FieldValue> fieldValueList = category3List.stream().map(category3 -> FieldValue.of(category3.getId())).collect(Collectors.toList());
        //  编写dsl语句;
        SearchResponse<AlbumInfoIndex> response = null;
        try {
            response = elasticsearchClient.search(f -> f.index("albuminfo")
                            .query(q -> q.terms(t -> t.field("category3Id").terms(ts -> ts.value(fieldValueList))))
                            .aggregations("agg_category3Id", a -> a.terms(t -> t.field("category3Id"))
                                    .aggregations("agg_top6", ag -> ag.topHits(th -> th.size(6)
                                            .sort(s -> s.field(f1 -> f1.field("hotScore").order(SortOrder.Desc)))))
                            )
                    ,
                    AlbumInfoIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        //  创建一个 List<Map> 对象
        //  List<Map> list = new ArrayList<>();
        Aggregate aggCategory3 = response.aggregations().get("agg_category3Id");
        //  获取数据
        List<Map<String, Object>> mapList = aggCategory3.lterms().buckets().array().stream().map(termsBucket -> {
            //  创建一个Map 对象
            HashMap<String, Object> map = new HashMap<>();
            long category3Id = termsBucket.key();
            //  获取专辑集合列表！
            Aggregate aggTop6 = termsBucket.aggregations().get("agg_top6");
            List<AlbumInfoIndex> albumInfoIndexList = aggTop6.topHits().hits().hits().stream().map(hit -> {
                //  获取数据
                JsonData source = hit.source();
                //  将数据转换为AlbumInfoIndex;
                AlbumInfoIndex albumInfoIndex = JSON.parseObject(source.toString(), AlbumInfoIndex.class);
                return albumInfoIndex;
            }).collect(Collectors.toList());
            //  baseCategory3 表示当前一个三级分类对象
            map.put("baseCategory3", category3Map.get(category3Id));
            map.put("list", albumInfoIndexList);
            return map;
        }).collect(Collectors.toList());
        //  返回数据
        return mapList;
    }

    @Override
    public List<String> completeSuggest(String keyword) {
        //  1.  通过动态生成dsl语句获取到检索结果集     2.  从结果集中获取到title字段数据
        SearchResponse<SuggestIndex> searchResponse = null;
        try {
            searchResponse = elasticsearchClient.search(s -> s.index("suggestinfo")
                            .suggest(s1->s1.suggesters("suggestionKeyword",
                                        s2->s2.prefix(keyword)
                                                .completion(c->c.field("keyword")
                                                        .skipDuplicates(true)
                                                        .fuzzy(f->f.fuzziness("auto"))))
                                    .suggesters("suggestionkeywordPinyin",
                                            s2->s2.prefix(keyword)
                                                    .completion(c->c.field("keywordPinyin")
                                                            .skipDuplicates(true)
                                                            .fuzzy(f->f.fuzziness("auto"))))
                                    .suggesters("suggestionkeywordSequence",
                                            s2->s2.prefix(keyword)
                                                    .completion(c->c.field("keywordSequence")
                                                            .skipDuplicates(true)
                                                            .fuzzy(f->f.fuzziness("auto"))))
                            )
                    ,
                    SuggestIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //  创建集合对象
        List<String> list = new ArrayList<>();

        //  获取提词数据
        list.addAll(this.getSuggestResultData(searchResponse,"suggestionKeyword"));
        list.addAll(this.getSuggestResultData(searchResponse,"suggestionkeywordPinyin"));
        list.addAll(this.getSuggestResultData(searchResponse,"suggestionkeywordSequence"));
        //  返回数据
        return list;
    }

    private Collection<String> getSuggestResultData(SearchResponse<SuggestIndex> searchResponse, String keyword) {
        //  获取到 suggest 集合列表
        List<Suggestion<SuggestIndex>> suggestionList = searchResponse.suggest().get(keyword);
        if (!CollectionUtils.isEmpty(suggestionList)){
            for (Suggestion<SuggestIndex> suggestIndexSuggestion : suggestionList) {
                List<String> stringList = suggestIndexSuggestion.completion().options().stream().map(option -> {
                    //  返回数据
                    return option.source().getTitle();
                }).collect(Collectors.toList());
                //  返回集合数据
                return stringList;
            }
        }
        //  默认返回空
        return null;
    }

    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        //  1.  生成到动态的dsl语句     2.  根据dsl语句进行查询数据   3.  获取到结果并赋值给AlbumSearchResponseVo对象
        SearchRequest searchRequest = queryBuildDsl(albumIndexQuery);
        SearchResponse<AlbumInfoIndex> searchResponse = null;
        try {
            searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //  创建 AlbumSearchResponseVo 对象 并赋值
        AlbumSearchResponseVo albumSearchResponseVo = parseResultData(searchResponse);
        //        private List<AlbumInfoIndexVo> list = new ArrayList<>();
        //        private Long total;//总记录数
        //        private Integer pageSize;//每页显示的内容
        //        private Integer pageNo;//当前页面
        //        private Long totalPages;// 总页数
        albumSearchResponseVo.setTotal(searchResponse.hits().total().value());
        albumSearchResponseVo.setPageSize(albumIndexQuery.getPageSize());
        albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());
        //  计算总页数 10 3 4 | 9 3 3
        Long totalPages = (albumSearchResponseVo.getTotal() + albumSearchResponseVo.getPageSize() - 1) / albumSearchResponseVo.getPageSize();
        albumSearchResponseVo.setTotalPages(totalPages);
        //  返回对象
        return albumSearchResponseVo;
    }

    /**
     * 赋值专辑集合数据
     *
     * @param searchResponse
     * @return
     */
    private AlbumSearchResponseVo parseResultData(SearchResponse<AlbumInfoIndex> searchResponse) {
        //  创建对象
        AlbumSearchResponseVo albumSearchResponseVo = new AlbumSearchResponseVo();
        //  private List<AlbumInfoIndexVo> list = new ArrayList<>();
        //  创建 一个集合
        List<AlbumInfoIndexVo> list = new ArrayList<>();
        for (Hit<AlbumInfoIndex> hit : searchResponse.hits().hits()) {
            //  创建 AlbumInfoIndexVo 对象
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            //  获取索引库中的数据
            AlbumInfoIndex albumInfoIndex = hit.source();
            //  属性拷贝
            BeanUtils.copyProperties(albumInfoIndex, albumInfoIndexVo);
            //  判断是否有高亮属性
            if (null != hit.highlight().get("albumTitle")) {
                //  获取高亮字段
                String albumTitle = hit.highlight().get("albumTitle").get(0);
                //  赋值
                albumInfoIndexVo.setAlbumTitle(albumTitle);
            }
            list.add(albumInfoIndexVo);
        }
        //  添加到集合中
        albumSearchResponseVo.setList(list);
        //  返回对象
        return albumSearchResponseVo;
    }

    /**
     * 生成动态的dsl语句
     *
     * @param albumIndexQuery
     * @return
     */
    private SearchRequest queryBuildDsl(AlbumIndexQuery albumIndexQuery) {
        //  创建一个对象 SearchRequest 对象
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
        searchRequestBuilder.index("albuminfo");
        //  创建一个BoolQuery对象
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();
        //  判断是否通过关键词检索.
        String keyword = albumIndexQuery.getKeyword();
        if (!StringUtils.isEmpty(keyword)) {
            boolQueryBuilder.should(s -> s.match(m -> m.field("albumTitle").query(keyword)))
                    .should(s -> s.match(m -> m.field("albumIntro").query(keyword)))
                    .should(s -> s.match(m -> m.field("announcerName").query(keyword)));
            //  高亮：
            searchRequestBuilder.highlight(h -> h.fields("albumTitle", h1 -> h1.preTags("<span style=color:red>").postTags("</span>")));
        }
        //  判断用户是否根据分类Id进行过滤
        if (!StringUtils.isEmpty(albumIndexQuery.getCategory1Id())) {
            boolQueryBuilder.filter(f -> f.term(t -> t.field("category1Id").value(albumIndexQuery.getCategory1Id())));
        }
        if (!StringUtils.isEmpty(albumIndexQuery.getCategory2Id())) {
            boolQueryBuilder.filter(f -> f.term(t -> t.field("category2Id").value(albumIndexQuery.getCategory2Id())));
        }
        if (!StringUtils.isEmpty(albumIndexQuery.getCategory3Id())) {
            boolQueryBuilder.filter(f -> f.term(t -> t.field("category3Id").value(albumIndexQuery.getCategory3Id())));
        }
        //  根据属性与属性值进行过滤! 属性id:属性值id ,使用字符串的split() 方法进行分割！
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (!CollectionUtils.isEmpty(attributeList)) {
            for (String attr : attributeList) {
                //  attr 进行分割：
                String[] split = attr.split(":");
                //  判断
                if (null != split && split.length == 2) {
                    //  属性Id
                    String attrId = split[0];
                    //  属性值Id
                    String attrValueId = split[1];
                    boolQueryBuilder.filter(f -> f.nested(n -> n.path("attributeValueIndexList")
                            .query(qy -> qy.bool(b -> b.filter(f1 -> f1.term(t -> t.field("attributeValueIndexList.attributeId").value(attrId)))
                                            .filter(f2 -> f2.term(t -> t.field("attributeValueIndexList.valueId").value(attrValueId)))
                                    )
                            )));
                }
            }
        }
        //  设置排序： 综合排序[1:desc] 播放量[2:desc] 发布时间[3:desc]；asc:升序 desc:降序）
        String order = albumIndexQuery.getOrder();
        if (!StringUtils.isEmpty(order)) {
            //  分割字符串
            String[] split = order.split(":");
            if (null != split && split.length == 2) {
                //  声明一个字段
                String field = "";
                switch (split[0]) {
                    case "1":
                        field = "hotScore";
                        break;
                    case "2":
                        field = "playStatNum";
                        break;
                    case "3":
                        field = "createTime";
                        break;
                }
                //  重新声明变量
                String finalField = field;
                searchRequestBuilder.sort(s -> s.field(f -> f.field(finalField).order("asc".equals(split[1]) ? SortOrder.Asc : SortOrder.Desc)));
            }
        }
        //  分页：
        int from = (albumIndexQuery.getPageNo() - 1) * albumIndexQuery.getPageSize();
        searchRequestBuilder.from(from);
        searchRequestBuilder.size(albumIndexQuery.getPageSize());
        BoolQuery boolQuery = boolQueryBuilder.build();
        //  调用query();
        searchRequestBuilder.query(boolQuery._toQuery());
        //  调用build(); 方法创建对象
        SearchRequest searchRequest = searchRequestBuilder.build();
        System.out.println("dsl:\t" + searchRequest.toString());
        //  返回对象
        return searchRequest;
    }

    @Override
    public void lowerAlbum(Long albumId) {
        albumIndexRepository.deleteById(albumId);
    }

    @Override
    public void upperAlbum(Long albumId) {
        //  给AlbumInfoIndex 赋值
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();

        //  利用异步编排实现远程调用，将串行变为并行！
        //  判断是否需要调用有返回值的方法依据：看当前这个对象中的属性是否作为下一个方法的参数！
        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            //  远程调用专辑微服务获取专辑对象
            Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfo(albumId);
            //  判断回调对象是否为空
            Assert.notNull(albumInfoResult, "专辑结果集不存在");
            AlbumInfo albumInfo = albumInfoResult.getData();
            Assert.notNull(albumInfo, "专辑对象不存在");
            //  直接属性拷贝
            BeanUtils.copyProperties(albumInfo, albumInfoIndex);
            return albumInfo;
        }, threadPoolExecutor);

        //  获取用户信息
        CompletableFuture<Void> userCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            //  远程调用
            Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfoVo(albumInfo.getUserId());
            //  判断
            Assert.notNull(userInfoVoResult, "用户信息不存在");
            UserInfoVo userInfoVo = userInfoVoResult.getData();
            Assert.notNull(userInfoVo, "用户信息不存在");
            albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
        }, threadPoolExecutor);


        //  获取分类数据
        CompletableFuture<Void> cateCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            Result<BaseCategoryView> categoryViewResult = categoryFeignClient.getCategoryView(albumInfo.getCategory3Id());
            Assert.notNull(categoryViewResult, "分类数据不存在");
            BaseCategoryView baseCategoryView = categoryViewResult.getData();
            Assert.notNull(baseCategoryView, "分类数据不存在");
            albumInfoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
        }, threadPoolExecutor);


        //  播放量：album_stat; 作业：
        albumInfoIndex.setPlayStatNum(new Random().nextInt(100000));
        albumInfoIndex.setCommentStatNum(new Random().nextInt(1000000));
        albumInfoIndex.setSubscribeStatNum(new Random().nextInt(10000000));
        albumInfoIndex.setBuyStatNum(new Random().nextInt(1000));
        //  热度：商品被点击的次数;
        albumInfoIndex.setHotScore((double) new Random().nextInt(1000000));

        //  获取属性数据
        CompletableFuture<Void> attrCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            //  获取专辑对应的属性与属性值集合;
            List<AlbumAttributeValue> albumAttributeValueVoList = albumInfo.getAlbumAttributeValueVoList();
            //  循环遍历
            if (!CollectionUtils.isEmpty(albumAttributeValueVoList)) {
                //            for (AlbumAttributeValue albumAttributeValue : albumAttributeValueVoList) {
                //                //  创建对象
                //                AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                //                BeanUtils.copyProperties(albumAttributeValue, attributeValueIndex);
                //
                //            }
                List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueVoList.stream().map(albumAttributeValue -> {
                    //  创建对象
                    AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                    //  属性拷贝
                    BeanUtils.copyProperties(albumAttributeValue, attributeValueIndex);
                    //  返回数据
                    return attributeValueIndex;
                }).collect(Collectors.toList());
                //  赋值：
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
            }
        }, threadPoolExecutor);

        //  数据汇总
        CompletableFuture.allOf(
                albumInfoCompletableFuture,
                userCompletableFuture,
                cateCompletableFuture,
                attrCompletableFuture
        ).join();
        //  将这个对象保存到es中!
        albumIndexRepository.save(albumInfoIndex);
        //  初始化索引库数据;
        SuggestIndex suggestIndexTitle = new SuggestIndex();
        suggestIndexTitle.setId(UUID.randomUUID().toString().replaceAll("-", ""));
        suggestIndexTitle.setTitle(albumInfoIndex.getAlbumTitle());
        suggestIndexTitle.setKeyword(new Completion(new String[]{albumInfoIndex.getAlbumTitle()}));
        suggestIndexTitle.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(albumInfoIndex.getAlbumTitle())}));
        suggestIndexTitle.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(albumInfoIndex.getAlbumTitle())}));
        suggestIndexRepository.save(suggestIndexTitle);
        //  简介：
        SuggestIndex suggestIndexInfo = new SuggestIndex();
        suggestIndexInfo.setId(UUID.randomUUID().toString().replaceAll("-", ""));
        suggestIndexInfo.setTitle(albumInfoIndex.getAlbumIntro());
        suggestIndexInfo.setKeyword(new Completion(new String[]{albumInfoIndex.getAlbumIntro()}));
        suggestIndexInfo.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(albumInfoIndex.getAlbumIntro())}));
        suggestIndexInfo.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(albumInfoIndex.getAlbumIntro())}));
        suggestIndexRepository.save(suggestIndexInfo);

    }
}
